iOS Interface Orientation & Autorotate

iOS屏幕方向相关定义

先简单介绍下iOS屏幕方向相关的定义。在iOS中有2个Interface Orientation相关的定义。从定义可以看出,UIDeviceOrientation是指设备的物理方向。UIInterfaceOrientation是指UI界面相对于设备的逻辑方向。在下面讨论的Orientation多是指UI界面的方向。

1.UIDeviceOrientation

typedef NS_ENUM(NSInteger, UIDeviceOrientation) {
    UIDeviceOrientationUnknown,
    UIDeviceOrientationPortrait,            
    UIDeviceOrientationPortraitUpsideDown,  
    UIDeviceOrientationLandscapeLeft,       
    UIDeviceOrientationLandscapeRight,      
    UIDeviceOrientationFaceUp,              
    UIDeviceOrientationFaceDown           
}

2.UIInterfaceOrientation

typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationUnknown            = UIDeviceOrientationUnknown,
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
}

控制屏幕方向的方式

iOS控制屏幕方向主要由下面3种方式完成。

Info.plist

使用Xcode建立一个iOS的项目,在Info.plist里面会有一个Supported interface orientations的Key,这个Key可以配置整个应用的屏幕方向。可以在General-Deployment Info下直接选择要支持的方向,就可以自动在Info.plist生成。这种是全局控制的方式。

AppDelegate

UIApplicationDelegate中,提供了如下一个方法,可以指定对应window的屏幕方向。如果应用只有一个window,可以视为全局控制。

- (UIInterfaceOrientationMask)application:(UIApplication *)application
  supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
    
}

ViewController

在`UIViewController`中,提供了如下两个方法,shouldAutorotate用来告知系统当前控制器是否要支持自动旋转,supportedInterfaceOrientations用来告知系统当前控制器支持的方向。

- (BOOL)shouldAutorotate
{
    return YES;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskAll;
}

Exception

在这2种方式下,如果出现支持方向上的分歧。系统会抛出类似下面的异常。

*** Terminating app due to uncaught exception 'UIApplicationInvalidInterfaceOrientation', reason: 'Supported orientations has no common orientation with the application, and [XXXViewController shouldAutorotate] is returning YES'

比如,当你在AppDelegate中返回的设备方向是UIInterfaceOrientationMaskLandscapeLeft.但是你在视图控制器中返回支持自动旋转。就会抛除异常。

屏幕方向与导航控制器

导航控制器方向控制

虽然导航控制器是UIViewController的子类,可以通过继承UINavigationController,然后通过复写上面ViewController提供的方法来对视图控制器的方向进行控制。但是其实UINavigationController的Delegate提供了下面这2个方法。

- (UIInterfaceOrientationMask)navigationControllerSupportedInterfaceOrientations:(UINavigationController *)navigationController
{
}

- (UIInterfaceOrientation)navigationControllerPreferredInterfaceOrientationForPresentation:(UINavigationController *)navigationController
{
}

第一个方法是来确定当前导航控制器所支持的方向。第二个方法是确定导航控制器初始化时,默认展示的方向。

设备方向变更后系统方法调用

  1. 当一个视图控制器将要显示时,视图控制器会询问用自己的shouldAutorotate方法,如果设备发生旋转的时候,是否自动旋转当前界面。

  2. 当iOS设备发生旋转时,系统会发出一个设备变更的通知。首先UIWindow会接收到这个通知,然后通过[UIWindow _updateToInterfaceOrientation:duration:force:]这个私有方法,设置UIWindow的方向。之后UIWindow会访问自己的rootViewController的supportedInterfaceOrientations:方法。之后会交给导航控制器的委托方法来处理,最终确定当前这次设备旋转是否需要旋转UI界面。

强制旋转UI界面方向

但有些时候,设备没有旋转,但是我们希望界面强制旋转到某一个方向,来实现特殊的需求。典型的需求就是视频播放器全屏播放这种。如果实现这种需求,有下面的一些办法。

1.通过setValue forKey的方式,强行修改UIDevice的方向,这样可以触发设备方向变更的通知。界面也就会旋转到需要的方向。

[[UIDevice currentDevice] setValue:[NSNumber numberWithInt:orientation] forKey:@"orientation"];

2.使用NSInvocation调用UIWindow的_updateToInterfaceOrientation私有方法。这个方法可以通过动画或非动画的方式更新UIWindow的方向,不会发出假的UIDevice的设备方向变更通知。下面的代码是我写的一个UIWindow的category。来实现这个方法的调用。

- (void)private_updateToInterfaceOrientation:(UIInterfaceOrientation)orientation animated:(BOOL)animated
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
    SEL mySelector = @selector(_updateToInterfaceOrientation:animated:);
#pragma clang diagnostic pop
    NSMethodSignature * sig = [[self class] instanceMethodSignatureForSelector:mySelector];
    NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature: sig];
    [myInvocation setTarget:self];
    [myInvocation setSelector: mySelector];
    [myInvocation setArgument:&orientation atIndex: 2];
    [myInvocation setArgument:&animated atIndex: 3];
    [myInvocation retainArguments];
    [myInvocation invoke];
}

3.可以直接旋转view的transform来强制你的视图方向。

导航控制器中单一页面横竖屏翻转如何支持

在现实项目中,业务情况可能会更复杂一些,会有一些页面是横竖屏全支持的,而一些页面只允许一个方向,这2种页面又都在同一个导航栈中。下面就实现了一个小Demo来演示这样的情况应该如何应对。Demo一共有3个页面。root ViewA ViewB View。UIWindow的RootView是一个导航控制器,导航控制器的RootView是root View。导航栈里面的结构是下面这样的。

TQRootViewController->TQAViewController->TQBViewController

其中TQRootViewController是不支持旋转的。TQAViewController是支持全部方向。TQBViewController也是不支持旋转的。具体实现参考下面的Demo。

InterfaceOrientationDemo


tinyq
101 声望0 粉丝